import store from "../store";
import { Vue } from "./install";
import ModuleCollection from "./module/module-colliection";
import { forEachValue } from "./utils";


function getState(store,path){
  return path.reduce((state,current)=>{
    return state[current]
  },store.state)
}
function installModule(store, path, module, rootState) {
  let namespace = store.modules.getNamespace(path);
  // ['a'，'c']
  if (path.length > 0) {
    // 是子模块 需要将数据处理到rootState上
    let parent = path.slice(0, -1).reduce((memo, current) => {
      return memo[current];
    }, rootState);
    // 添加一个不存在的属性需要使用Vue.set方法 否则无效
    store.withCommitting(()=>{
      Vue.set(parent,path[path.length - 1],module.state)
    })
  }
  module.forEachMutation((mutationKey, mutationValue) => {
    store.mutations[namespace+mutationKey] = store.mutations[namespace+mutationKey] || [];
    store.mutations[namespace+mutationKey].push((payload) => {
      store.withCommitting(()=>{
        mutationValue(getState(store,path), payload);
      })
      store._subscribes.forEach(callback=>callback({type:mutationKey},store.state))
    });
  });
  module.forEachAction((actionKey, actionValue) => {
    store.actions[namespace+actionKey] = store.actions[namespace+actionKey] || [];
    store.actions[namespace+actionKey].push(async (payload) => {
      return actionValue(store, payload);
    });
  });
  // 这里有一个问题 对于我们的getter而言，
  module.forEachGetter((getterKey, getterValue) => {
    // 应该先判断一下store.getters 是否有这个属性 如果有就不会要在注册
    if (store.wrapperGetters[namespace+getterKey]) return console.error("duplicate key");
    store.wrapperGetters[namespace+getterKey] = () => {
      return getterValue(getState(store,path));
    };
  });
  module.forEachChildren((childName, childValue) => {
    // [a] [b]  [a,c]
    installModule(store, path.concat(childName), childValue, rootState);
  });
}

function resetVM(store, state) {
  let oldVm = store.vm; // 拿到以前的
  const computed = {};
  store.getters = {};
  forEachValue(store.wrapperGetters, (getterKey, getterValue) => {
    computed[getterKey] = () => getterValue(store.state); // 计算属性缓存
    Object.defineProperty(store.getters, getterKey, {
      // proxy
      get: () => store.vm[getterKey], // vuex中getters就只有get没有set
    });
  });

  store.vm = new Vue({
    // 数据变了需要更新视图 只有data中的数据会被观测
    data: {
      $$state: state,
    },
    computed,
  });
  // exprOrFn
  store.vm.$watch(()=>store.vm._data.$$state,()=>{
    console.assert(store._committing,'outside mutation ....')
  },{deep:true,sync:true})

  if(oldVm){
    Vue.nextTick(()=>oldVm.$destroy()); // 卸载之前重新搞一个新的实例
  }
}
class Store {
  constructor(options) {
    // 为了将用户的参数转换成我好理解的属性
    this.modules = new ModuleCollection(options);
    this.actions = {}; // {addAge:[]}
    this.mutations = {}; // {addAge:[]}
    this._committing = false; // 不是在mutation中修改的
    this.wrapperGetters = {}; // $store.getters
    // 除了安装mutation action getters 这些之外 最重要的是状态
    const state = options.state;
    this._subscribes = []
    installModule(this, [], this.modules.root, state); // state根模块的状态对象

    resetVM(this, state);

    if(options.plugins){
      options.plugins.forEach(plugin=>plugin(this))
    }
  }
  withCommitting(fn){
    this._committing = true;
    fn(); // 如果这个函数是异步执行的，那么_commiting会先变为false
    this._committing = false;
  }
  replaceState(newState){ // 如果直接将状态替换会导致，代码里用的是以前的，状态显示的是最新，改以前的不会导致视图刷新
    this.withCommitting(()=>{
      this.vm._data.$$state = newState
    })
  }
  subscribe(callback){
    this._subscribes.push(callback)
  }
  get state() {
    return this.vm._data.$$state;
  }
  dispatch = async (actionKey, payload) => {
    // this.actuons
    let actions = this.actions[actionKey];
    if(actions){
      return Promise.all(actions.map(action=>action(payload)))
    }  
  };
  commit = (mutationKey, payload) => {
    //this.mutations
    let mutations = this.mutations[mutationKey];
    mutations && mutations.forEach((fn) => fn(payload));
  };

  // 动态注册的原理就是把vuex的注册逻辑重新来一遍
  // 先注册进去，在进行安装  重新创造实例
  registerModule(path,rawModule){
    // 如果是一个字符串就包装成数组 
      if(!Array.isArray(path)) path = [path];
      this.modules.register(path,rawModule);
      // 安装模块的时候 需要传入的是包装后的不是用户写的
      installModule(this,path,rawModule.wrapperModule,this.state); // 将自己生产的模块装到全局上
      resetVM(this, this.state); 
  }
}

// Vuex中的插件机制， vuex数据刷新后丢失怎么办 ？ 存到本地，每次加载获取一下. 每次调用接口拿到数据塞到vuex中

export default Store;
